use anyhow::Result;
use axum::{
    Router,
    extract::{Extension, Path},
    http::{HeaderMap, HeaderValue, StatusCode},
    routing::get,
};
use bytes::Bytes;
use lru::LruCache;
use percent_encoding::{NON_ALPHANUMERIC, percent_decode_str, percent_encode};
use serde::Deserialize;
use std::{
    collections::hash_map::DefaultHasher,
    convert::TryInto,
    hash::{Hash, Hasher},
    num::NonZeroUsize,
    sync::Arc,
};
use tokio::sync::Mutex;
use tower::ServiceBuilder;
use tracing::{info, instrument};
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt};

// 引入 protobuf 生成的代码，我们暂且不用太关心他们
mod pb;

use pb::*;

mod engine;
use engine::{Engine, Photon};
use image::ImageFormat;

// 参数使用 serde 做 Deserialize，axum 会自动识别并解析
#[derive(Deserialize)]
struct Params {
    spec: String,
    url: String,
}

// 定义缓存类型的别名
type Cache = Arc<Mutex<LruCache<u64, Bytes>>>;

#[tokio::main(flavor = "current_thread")]
async fn main() {
    // 初始化 tracing：只有注册 subscriber 后， 才能在控制台上看到日志输出
    // tracing_subscriber::fmt::init(); // 过时
    tracing_subscriber::registry().with(fmt::layer()).init();
    let cache: Cache = Arc::new(Mutex::new(LruCache::new(NonZeroUsize::new(1024).unwrap())));

    // 构建路由
    let app = Router::new()
        // `GET /image` 会执行 generate 函数，并把 spec 和 url 传递过去
        .route("/image/{spec}/{url}", get(generate))
        // ​问题：AddExtensionLayer 在新版中已被 Extension 替代
        // .layer(Extension(cache));
        .layer(ServiceBuilder::new().layer(Extension(cache)));

    // 运行 web 服务器
    let addr: &str = "127.0.0.1:3000";

    print_test_url(
        "https://images.pexels.com/photos/1562477/pexels-photo-1562477.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260",
    );

    print_test_url("https://www.gstatic.cn/webp/gallery3/1_webp_ll.png");

    // TODO: GIF图片需要进行特殊处理，目前GIF图片进行ImageSpecs处理后输出的是静态图片，而不是原先的GIF动图
    print_test_url("https://cdn.pixabay.com/animation/2023/03/02/17/51/17-51-48-622_512.gif");

    info!("Listening on {}", addr);
    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    // ​问题：axum::Server::bind(&addr).serve(...) 已废弃，新版直接使用 axum::serve
    axum::serve(listener, app).await.unwrap();
}

// 目前我们就只把参数解析出来
async fn generate(
    Path(Params { spec, url }): Path<Params>,
    Extension(cache): Extension<Cache>,
) -> Result<(HeaderMap, Vec<u8>), StatusCode> {
    let spec: ImageSpec = spec
        .as_str()
        .try_into()
        .map_err(|_| StatusCode::BAD_REQUEST)?;

    let url: &str = &percent_decode_str(&url).decode_utf8_lossy();
    let data = retrieve_image(&url, cache)
        .await
        .map_err(|_| StatusCode::BAD_REQUEST)?;

    // 自动检测图片格式
    let format = image::guess_format(&data).map_err(|_| StatusCode::UNSUPPORTED_MEDIA_TYPE)?;
    info!("Detected image format: {:?}", format);

    // 使用 image engine 处理
    let mut engine: Photon = data
        .try_into()
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
    engine.apply(&spec.specs);

    // 将 engine.generate() 改为使用检测到的图片格式
    let image = engine.generate(format);
    info!("Finished processing: image size {}", image.len());

    let mut headers = HeaderMap::new();
    headers.insert(
        "content-type",
        match format {
            ImageFormat::Png => HeaderValue::from_static("image/png"),
            ImageFormat::Jpeg => HeaderValue::from_static("image/jpeg"),
            ImageFormat::Gif => HeaderValue::from_static("image/gif"),
            // ImageFormat::WebP => HeaderValue::from_static("image/webp"),
            _ => HeaderValue::from_static("image/jpeg"), // 默认使用 JPEG 格式
        },
    );

    info!("Image[{format:?}] generation has completed");
    Ok((headers, image))
}

// 对源图像进行缓存处理
#[instrument(level = "info", skip(cache))]
async fn retrieve_image(url: &str, cache: Cache) -> Result<Bytes> {
    let mut hasher = DefaultHasher::new();
    url.hash(&mut hasher);
    let key = hasher.finish();

    let g = &mut cache.lock().await;
    let data = match g.get(&key) {
        Some(v) => {
            info!("Match cache {}", key);
            v.to_owned()
        }
        None => {
            info!("Retrieve url");
            let resp = reqwest::get(url).await?;
            let data = resp.bytes().await?;
            g.put(key, data.clone());
            data
        }
    };

    Ok(data)
}

// 调试辅助函数
fn print_test_url(url: &str) {
    use std::borrow::Borrow;
    let spec1 = Spec::new_resize(500, 800, resize::SampleFilter::CatmullRom);
    let spec2 = Spec::new_watermark(20, 20);
    let spec3 = Spec::new_filter(filter::Filter::Marine);
    let image_spec = ImageSpec::new(vec![spec1, spec2, spec3]);
    let s: String = image_spec.borrow().into();
    let test_image = percent_encode(url.as_bytes(), NON_ALPHANUMERIC).to_string();
    // println!("test url: http://localhost:3000/image/{}/{}", s, test_image);
    println!("test url: http://localhost:3000/image/{}/{}", s, test_image)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    // 测试 JPEG格式 效果
    fn test_jpeg() {
        print_test_url(
            "https://images.pexels.com/photos/1562477/pexels-photo-1562477.jpeg?auto=compress&cs=tinysrgb&dpr=3&h=750&w=1260",
        );
    }

    #[test]
    // 测试 GIF 格式效果
    fn test_gif() {
        print_test_url("https://cdn.pixabay.com/animation/2023/03/02/17/51/17-51-48-622_512.gif");
    }

    #[test]
    // 测试 PNG 格式效果
    fn test_png() {
        print_test_url("https://www.gstatic.cn/webp/gallery3/1_webp_ll.png");
    }
}
